package com.gitee.wsl.time.chinese

import com.gitee.wsl.text.format.NumberChineseFormatter
import com.gitee.wsl.text.format.format
import com.gitee.wsl.time.Zodiac.getChineseZodiac
import com.gitee.wsl.time.chinese.LunarFestival.getFestivals
import com.gitee.wsl.time.chinese.GanZhi.getGanzhiOfDay
import com.gitee.wsl.time.chinese.GanZhi.getGanzhiOfMonth
import com.gitee.wsl.time.chinese.GanZhi.getGanzhiOfYear
import com.gitee.wsl.time.chinese.util.LunarInfo
import com.gitee.wsl.time.toLocalDate
import kotlinx.datetime.LocalDate
import kotlin.jvm.JvmOverloads


/**
 * 农历日期工具，最大支持到2099年，支持：
 *
 *
 *  * 通过公历日期构造获取对应农历
 *  * 通过农历日期直接构造
 *
 *
 * @author zjw, looly
 * @since 5.1.1
 */
class ChineseDate {
    /**
     * 获得农历年份
     *
     * @return 返回农历年份
     */
    //农历年
    val chineseYear: Int

    /**
     * 获取农历的月，从1开始计数<br></br>
     * 此方法返回实际的月序号，如一月是闰月，则一月返回1，润一月返回2
     *
     * @return 农历的月
     * @since 5.2.4
     */
    //农历月，润N月这个值就是N+1，其他月按照显示月份赋值
    val month: Int

    /**
     * 当前农历月份是否为闰月
     *
     * @return 是否为闰月
     * @since 5.4.2
     */
    // 当前月份是否闰月
    val isLeapMonth: Boolean

    /**
     * 获取农历的日，从1开始计数
     *
     * @return 农历的日，从1开始计数
     * @since 5.2.4
     */
    //农历日
    val day: Int

    /**
     * 获取公历的年
     *
     * @return 公历年
     * @since 5.6.1
     */
    //公历年
    val gregorianYear: Int

    /**
     * 获取公历的月，从1开始计数
     *
     * @return 公历月
     * @since 5.6.1
     */
    //公历月，从1开始计数
    val gregorianMonthBase1: Int

    /**
     * 获取公历的日
     *
     * @return 公历日
     * @since 5.6.1
     */
    //公历日
    val gregorianDay: Int

    /**
     * 通过公历日期构造
     *
     * @param localDate 公历日期
     * @since 5.7.22
     */
    constructor(localDate: LocalDate) {
        // 公历
        this.gregorianYear = localDate.year
        this.gregorianMonthBase1 = localDate.monthNumber
        this.gregorianDay = localDate.dayOfMonth

        // 求出和1900年1月31日相差的天数
        var offset = (localDate.toEpochDays() - LunarInfo.BASE_DAY) as Int

        // 计算农历年份
        // 用offset减去每农历年的天数，计算当天是农历第几天，offset是当年的第几天
        var daysOfYear: Int
        var iYear: Int = LunarInfo.BASE_YEAR
        while (iYear <= LunarInfo.MAX_YEAR) {
            daysOfYear = LunarInfo.daysInLunarYear(iYear)
            if (offset < daysOfYear) {
                break
            }
            offset -= daysOfYear
            iYear++
        }

        this.chineseYear = iYear
        // 计算农历月份
        val leapMonth: Int = LunarInfo.leapMonth(iYear) // 闰哪个月,1-12
        // 用当年的天数offset,逐个减去每月（农历）的天数，求出当天是本月的第几天
        var month: Int
        var daysOfMonth: Int
        var hasLeapMonth = false
        month = 1
        while (month < 13) {
            // 闰月，如润的是五月，则5表示五月，6表示润五月
            if (leapMonth > 0 && month == (leapMonth + 1)) {
                daysOfMonth = LunarInfo.leapDays(this.chineseYear)
                hasLeapMonth = true
            } else {
                // 普通月，当前面的月份存在闰月时，普通月份要-1，递补闰月的数字
                // 如2月是闰月，此时3月实际是第四个月
                daysOfMonth = LunarInfo.daysInMonth(this.chineseYear, if (hasLeapMonth) month - 1 else month)
            }

            if (offset < daysOfMonth) {
                // offset不足月，结束
                break
            }
            offset -= daysOfMonth
            month++
        }

        this.isLeapMonth = leapMonth > 0 && (month == (leapMonth + 1))
        if (hasLeapMonth && !this.isLeapMonth) {
            // 当前月份前有闰月，则月份显示要-1，除非当前月份就是润月
            month--
        }
        this.month = month
        this.day = offset + 1
    }

    /**
     * 构造方法传入日期<br></br>
     * 通过isLeapMonth参数区分是否闰月，如五月是闰月，当isLeapMonth为`true`时，表示润五月，`false`表示五月
     *
     * @param chineseYear  农历年
     * @param chineseMonth 农历月，1表示一月（正月），如果isLeapMonth为`true`，1表示润一月
     * @param chineseDay   农历日，1表示初一
     * @param isLeapMonth  当前月份是否闰月
     * @since 5.7.18
     */
    /**
     * 构造方法传入日期<br></br>
     * 此方法自动判断闰月，如果chineseMonth为本年的闰月，则按照闰月计算
     *
     * @param chineseYear  农历年
     * @param chineseMonth 农历月，1表示一月（正月）
     * @param chineseDay   农历日，1表示初一
     * @since 5.2.4
     */
    @JvmOverloads
    constructor(
        chineseYear: Int,
        chineseMonth: Int,
        chineseDay: Int,
        isLeapMonth: Boolean = chineseMonth == LunarInfo.leapMonth(chineseYear),
    ) {
        var isLeapMonth = isLeapMonth
        if (chineseMonth != LunarInfo.leapMonth(chineseYear)) {
            // issue#I5YB1A，用户传入的月份可能非闰月，此时此参数无效。
            isLeapMonth = false
        }

        this.day = chineseDay
        // 当月是闰月的后边的月定义为闰月，如润的是五月，则5表示五月，6表示润五月
        this.isLeapMonth = isLeapMonth
        // 闰月时，农历月份+1，如6表示润五月
        this.month = if (isLeapMonth) chineseMonth + 1 else chineseMonth
        this.chineseYear = chineseYear

        val dateTime = lunar2solar(chineseYear, chineseMonth, chineseDay, isLeapMonth)
        if (null != dateTime) {
            //初始化公历年
            this.gregorianDay = dateTime.dayOfMonth
            //初始化公历月
            this.gregorianMonthBase1 = dateTime.monthNumber
            //初始化公历日
            this.gregorianYear = dateTime.year
        } else {
            //初始化公历年
            this.gregorianDay = -1
            //初始化公历月
            this.gregorianMonthBase1 = -1
            //初始化公历日
            this.gregorianYear = -1
        }
    }

    val gregorianMonth: Int
        /**
         * 获取公历的月，从0开始计数
         *
         * @return 公历月
         * @since 5.6.1
         */
        get() = this.gregorianMonthBase1 - 1


    val chineseMonth: String
        /**
         * 获得农历月份（中文，例如二月，十二月，或者润一月）
         *
         * @return 返回农历月份
         */
        get() = getChineseMonth(false)

    val chineseMonthName: String
        /**
         * 获得农历月称呼（中文，例如二月，腊月，或者润正月）
         *
         * @return 返回农历月份称呼
         */
        get() = getChineseMonth(true)

    /**
     * 获得农历月份（中文，例如二月，十二月，或者润一月）
     *
     * @param isTraditional 是否传统表示，例如一月传统表示为正月
     * @return 返回农历月份
     * @since 5.7.18
     */
    fun getChineseMonth(isTraditional: Boolean): String {
        return ChineseMonth.getChineseMonthName(
            this.isLeapMonth,
            if (this.isLeapMonth) this.month - 1 else this.month, isTraditional
        )
    }

    /**
     * 获得农历日
     *
     * @return 获得农历日
     */
    val chineseDay: String
        get() {
            val chineseTen = arrayOf<String>("初", "十", "廿", "卅")
            val n = if (day % 10 == 0) 9 else (day % 10 - 1)
            if (day > 30) {
                return ""
            }
            when (day) {
                10 -> return "初十"
                20 -> return "二十"
                30 -> return "三十"
                else -> return chineseTen[day / 10] + NumberChineseFormatter.format(n + 1, false)
            }
        }

    val gregorianDate: LocalDate
        /**
         * 获取公历的Date
         *
         * @return 公历Date
         * @since 5.6.1
         */
        get() = LocalDate(gregorianYear,gregorianMonthBase1,gregorianDay)

//    val gregorianCalendar: Calendar
//        /**
//         * 获取公历的Calendar
//         *
//         * @return 公历Calendar
//         * @since 5.6.1
//         */
//        get() {
//            val calendar: Calendar = CalendarUtil.calendar()
//            calendar.set(this.gregorianYear, this.gregorianMonth, this.gregorianDay, 0, 0, 0)
//            return calendar
//        }

    val festivals: String
        /**
         * 获得节日，闰月不计入节日中
         *
         * @return 获得农历节日
         */
        get() = getFestivals(this.chineseYear, if (this.isLeapMonth) this.month - 1 else this.month, day).joinToString(",")

    val chineseZodiac: String?
        /**
         * 获得年份生肖
         *
         * @return 获得年份生肖
         */
        get() = getChineseZodiac(this.chineseYear)


    val cyclical: String
        /**
         * 获得年的天干地支
         *
         * @return 获得天干地支
         */
        get() = getGanzhiOfYear(this.chineseYear)

    val cyclicalYMD: String?
        /**
         * 干支纪年信息
         *
         * @return 获得天干地支的年月日信息
         */
        get() {
            if (this.gregorianYear >= LunarInfo.BASE_YEAR && this.gregorianMonthBase1 > 0 && this.gregorianDay > 0) {
                return cyclicalm(this.gregorianYear, this.gregorianMonthBase1, this.gregorianDay)
            }
            return null
        }


    val term: String
        /**
         * 获得节气
         *
         * @return 获得节气
         * @since 5.6.3
         */
        get() = SolarTerm.getTerm(this.gregorianYear, this.gregorianMonthBase1, this.gregorianDay)?:""

    /**
     * 转换为标准的日期格式来表示农历日期，例如2020-01-13<br></br>
     * 如果存在闰月，显示闰月月份，如润二月显示2
     *
     * @return 标准的日期格式
     * @since 5.2.4
     */
    fun toStringNormal(): String {
        return String.format(
            "%04d-%02d-%02d", this.chineseYear,
            if (this.isLeapMonth) this.month - 1 else this.month, this.day
        )
    }

    override fun toString(): String {
        return String.format(
            "%s%s年 %s%s",
            this.cyclical,
            this.chineseZodiac,
            this.chineseMonthName,
            this.chineseDay
        )
    }

    // ------------------------------------------------------- private method start
    /**
     * 这里同步处理年月日的天干地支信息
     *
     * @param year  公历年
     * @param month 公历月，从1开始
     * @param day   公历日
     * @return 天干地支信息
     */
    private fun cyclicalm(year: Int, month: Int, day: Int): String {
        return String.format(
            "{}年{}月{}日",
            getGanzhiOfYear(this.chineseYear),
            getGanzhiOfMonth(year, month, day),
            getGanzhiOfDay(year, month, day)
        )
    }

    /**
     * 通过农历年月日信息 返回公历信息 提供给构造函数
     *
     * @param chineseYear  农历年
     * @param chineseMonth 农历月
     * @param chineseDay   农历日
     * @param isLeapMonth  传入的月是不是闰月
     * @return 公历信息
     */
    private fun lunar2solar(chineseYear: Int, chineseMonth: Int, chineseDay: Int, isLeapMonth: Boolean): LocalDate? {
        //超出了最大极限值
        if ((chineseYear == 2100 && chineseMonth == 12 && chineseDay > 1) ||
            (chineseYear == LunarInfo.BASE_YEAR && chineseMonth == 1 && chineseDay < 31)
        ) {
            return null
        }
        val day: Int = LunarInfo.daysInMonth(chineseYear, chineseMonth)
        var _day = day
        if (isLeapMonth) {
            _day = LunarInfo.leapDays(chineseYear)
        }
        //参数合法性效验
        if (chineseYear < LunarInfo.BASE_YEAR || chineseYear > 2100 || chineseDay > _day) {
            return null
        }
        //计算农历的时间差
        var offset = 0
        for (i in LunarInfo.BASE_YEAR..<chineseYear) {
            offset += LunarInfo.daysInLunarYear(i)
        }
        var leap: Int
        var isAdd = false
        for (i in 1..<chineseMonth) {
            leap = LunarInfo.leapMonth(chineseYear)
            if (!isAdd) { //处理闰月
                if (leap <= i && leap > 0) {
                    offset += LunarInfo.leapDays(chineseYear)
                    isAdd = true
                }
            }
            offset += LunarInfo.daysInMonth(chineseYear, i)
        }
        //转换闰月农历 需补充该年闰月的前一个月的时差
        if (isLeapMonth) {
            offset += day
        }
        //1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点) -2203804800000
        return (((offset + chineseDay - 31) * 86400000L) - 2203804800000L).toLocalDate()
    } // ------------------------------------------------------- private method end
}
